// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "intel-i2c-controller.h"

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <fuchsia/hardware/i2c/c/fidl.h>
#include <lib/device-protocol/pci.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#include <zircon/listnode.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>

#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/protocol/auxdata.h>
#include <ddk/protocol/i2c.h>
#include <ddk/protocol/pci.h>
#include <hw/pci.h>
#include <hw/reg.h>

#include "binding.h"
#include "intel-i2c-subordinate.h"

#define DEVIDLE_CONTROL 0x24c
#define DEVIDLE_CONTROL_CMD_IN_PROGRESS 0
#define DEVIDLE_CONTROL_DEVIDLE 2
#define DEVIDLE_CONTROL_RESTORE_REQUIRED 3

#define ACER_I2C_TOUCH INTEL_SUNRISE_POINT_SERIALIO_I2C1_DID

// Number of entries at which the FIFO level triggers happen
#define DEFAULT_RX_FIFO_TRIGGER_LEVEL 8
#define DEFAULT_TX_FIFO_TRIGGER_LEVEL 8

// Signals used on the controller's event_handle
#define RX_FULL_SIGNAL ZX_USER_SIGNAL_0
#define TX_EMPTY_SIGNAL ZX_USER_SIGNAL_1
#define STOP_DETECTED_SIGNAL ZX_USER_SIGNAL_2
#define ERROR_DETECTED_SIGNAL ZX_USER_SIGNAL_3

// More than enough
#define MAX_TRANSFER_SIZE (UINT16_MAX - 1)

#define INTEL_DESIGNWARE_COMP_TYPE 0x44570140

// Implement the functionality of the i2c bus device.

static void intel_i2c_transact(void* ctx, const i2c_op_t ops[], size_t cnt,
                               i2c_transact_callback transact_cb, void* cookie) {
  intel_serialio_i2c_subordinate_device_t* subordinate = ctx;
  i2c_subordinate_segment_t segs[I2C_MAX_RW_OPS];
  if (cnt >= I2C_MAX_RW_OPS) {
    transact_cb(cookie, ZX_ERR_NOT_SUPPORTED, NULL, 0);
    return;
  }
  uint8_t* read_buffer = malloc(MAX_TRANSFER_SIZE);
  if (read_buffer == NULL) {
    zxlogf(ERROR, "intel-i2c-controller: out of memory\n");
    transact_cb(cookie, ZX_ERR_NO_MEMORY, NULL, 0);
    return;
  }
  uint8_t* p_reads = read_buffer;
  for (size_t i = 0; i < cnt; ++i) {
    if (ops[i].is_read) {
      segs[i].buf = p_reads;
      segs[i].type = fuchsia_hardware_i2c_SegmentType_READ;
      p_reads += ops[i].data_size;
      if (p_reads - read_buffer > MAX_TRANSFER_SIZE) {
        free(read_buffer);
        transact_cb(cookie, ZX_ERR_INVALID_ARGS, NULL, 0);
        return;
      }
    } else {
      segs[i].buf = (void*)ops[i].data_buffer;
      segs[i].type = fuchsia_hardware_i2c_SegmentType_WRITE;
    }
    segs[i].len = ops[i].data_size;
  }
  zx_status_t status = intel_serialio_i2c_subordinate_transfer(subordinate, segs, cnt);
  if (status != ZX_OK) {
    zxlogf(ERROR, "intel-i2c-controller: intel_serialio_i2c_subordinate_transfer: %d\n", status);
    free(read_buffer);
    transact_cb(cookie, status, NULL, 0);
    return;
  }
  i2c_op_t read_ops[I2C_MAX_RW_OPS];
  size_t read_ops_cnt = 0;
  p_reads = read_buffer;
  for (size_t i = 0; i < cnt; ++i) {
    if (ops[i].is_read) {
      read_ops[read_ops_cnt] = ops[i];
      read_ops[read_ops_cnt].data_buffer = p_reads;
      read_ops_cnt++;
      p_reads += ops[i].data_size;
    }
  }
  transact_cb(cookie, status, read_ops, read_ops_cnt);
  free(read_buffer);
  return;
}

static zx_status_t intel_i2c_get_max_transfer_size(void* ctx, size_t* out_size) {
  *out_size = MAX_TRANSFER_SIZE;
  return ZX_OK;
}

static uint32_t intel_i2c_extract_tx_fifo_depth_from_param(uint32_t param) {
  return ((param >> 16) & 0xff) + 1;
}

static uint32_t intel_i2c_extract_rx_fifo_depth_from_param(uint32_t param) {
  return ((param >> 8) & 0xff) + 1;
}

static zx_status_t intel_i2c_get_interrupt(void* ctx, uint32_t flags, zx_handle_t* out_handle) {
  intel_serialio_i2c_subordinate_device_t* subordinate = ctx;
  return intel_serialio_i2c_subordinate_get_irq(subordinate, out_handle);
}

i2c_protocol_ops_t i2c_protocol_ops = {
    .transact = intel_i2c_transact,
    .get_max_transfer_size = intel_i2c_get_max_transfer_size,
    .get_interrupt = intel_i2c_get_interrupt,
};

static uint32_t chip_addr_mask(int width) { return ((1 << width) - 1); }

static zx_status_t intel_serialio_i2c_find_subordinate(
    intel_serialio_i2c_subordinate_device_t** subordinate, intel_serialio_i2c_device_t* device,
    uint16_t address) {
  assert(subordinate);

  list_for_every_entry (&device->subordinate_list, *subordinate,
                        intel_serialio_i2c_subordinate_device_t, subordinate_list_node) {
    if ((*subordinate)->chip_address == address)
      return ZX_OK;
  }

  return ZX_ERR_NOT_FOUND;
}

static zx_status_t intel_serialio_i2c_add_subordinate(intel_serialio_i2c_device_t* device,
                                                      uint8_t width, uint16_t address,
                                                      uint32_t protocol_id,
                                                      zx_device_prop_t* moreprops,
                                                      uint32_t propcount) {
  zx_status_t status;

  if ((width != I2C_7BIT_ADDRESS && width != I2C_10BIT_ADDRESS) ||
      (address & ~chip_addr_mask(width)) != 0) {
    return ZX_ERR_INVALID_ARGS;
  }

  intel_serialio_i2c_subordinate_device_t* subordinate;

  mtx_lock(&device->mutex);

  // Make sure a subordinate with the given address doesn't already exist.
  status = intel_serialio_i2c_find_subordinate(&subordinate, device, address);
  if (status == ZX_OK) {
    status = ZX_ERR_ALREADY_EXISTS;
  }
  if (status != ZX_ERR_NOT_FOUND) {
    mtx_unlock(&device->mutex);
    return status;
  }

  subordinate = calloc(1, sizeof(*subordinate));
  if (!subordinate) {
    status = ZX_ERR_NO_MEMORY;
    mtx_unlock(&device->mutex);
    return status;
  }
  subordinate->chip_address_width = width;
  subordinate->chip_address = address;
  subordinate->controller = device;

  list_add_head(&device->subordinate_list, &subordinate->subordinate_list_node);
  mtx_unlock(&device->mutex);

  // Temporarily add binding support for the i2c subordinate. The real way to do
  // this will involve ACPI/devicetree enumeration, but for now we publish PCI
  // VID/DID and i2c ADDR as binding properties.
  pci_protocol_t pci;
  status = device_get_protocol(device->pcidev, ZX_PROTOCOL_PCI, &pci);
  if (status != ZX_OK) {
    goto fail;
  }

  uint16_t vendor_id;
  uint16_t device_id;
  int count = 0;

  pci_config_read16(&pci, PCI_CONFIG_VENDOR_ID, &vendor_id);
  pci_config_read16(&pci, PCI_CONFIG_DEVICE_ID, &device_id);

  zx_device_prop_t props[8];
  if (countof(props) < 3 + propcount) {
    zxlogf(ERROR, "i2c: subordinate at 0x%02x has too many props! (%u)\n", address, propcount);
    status = ZX_ERR_INVALID_ARGS;
    goto fail;
  }
  props[count++] = (zx_device_prop_t){BIND_PCI_VID, 0, vendor_id};
  props[count++] = (zx_device_prop_t){BIND_PCI_DID, 0, device_id};
  props[count++] = (zx_device_prop_t){BIND_TOPO_I2C, 0, BIND_TOPO_I2C_PACK(address)};
  memcpy(&props[count], moreprops, sizeof(zx_device_prop_t) * propcount);
  count += propcount;

  char name[sizeof(address) * 2 + 2] = {
      [sizeof(name) - 1] = '\0',
  };
  snprintf(name, sizeof(name) - 1, "%04x", address);

  device_add_args_t args = {
      .version = DEVICE_ADD_ARGS_VERSION,
      .name = name,
      .ctx = subordinate,
      .ops = &intel_serialio_i2c_subordinate_device_proto,
      .proto_id = protocol_id,
      .props = props,
      .prop_count = count,
  };

  if (protocol_id == ZX_PROTOCOL_I2C) {
    args.proto_ops = &i2c_protocol_ops;
  }

  status = device_add(device->zxdev, &args, &subordinate->zxdev);
  if (status != ZX_OK) {
    goto fail;
  }
  return ZX_OK;

fail:
  mtx_lock(&device->mutex);
  list_delete(&subordinate->subordinate_list_node);
  mtx_unlock(&device->mutex);
  free(subordinate);
  return status;
}

static zx_status_t intel_serialio_i2c_remove_subordinate(intel_serialio_i2c_device_t* device,
                                                         uint8_t width, uint16_t address) {
  zx_status_t status;

  if ((width != I2C_7BIT_ADDRESS && width != I2C_10BIT_ADDRESS) ||
      (address & ~chip_addr_mask(width)) != 0) {
    return ZX_ERR_INVALID_ARGS;
  }

  intel_serialio_i2c_subordinate_device_t* subordinate;

  mtx_lock(&device->mutex);

  // Find the subordinate we're trying to remove.
  status = intel_serialio_i2c_find_subordinate(&subordinate, device, address);
  if (status < 0)
    goto remove_subordinate_finish;
  if (subordinate->chip_address_width != width) {
    zxlogf(ERROR, "Chip address width mismatch.\n");
    status = ZX_ERR_NOT_FOUND;
    goto remove_subordinate_finish;
  }

  status = device_remove(subordinate->zxdev);
  if (status < 0)
    goto remove_subordinate_finish;

  list_delete(&subordinate->subordinate_list_node);
  free(subordinate);

remove_subordinate_finish:
  mtx_unlock(&device->mutex);
  return status;
}

static uint32_t intel_serialio_compute_scl_hcnt(uint32_t controller_freq, uint32_t t_high_nanos,
                                                uint32_t t_r_nanos) {
  uint32_t clock_freq_kilohz = controller_freq / 1000;

  // We need high count to satisfy highcount + 3 >= clock * (t_HIGH + t_r_max)
  // Apparently the counter starts as soon as the controller releases SCL, so
  // include t_r to account for potential delay in rising.
  //
  // In terms of units, the division should really be thought of as a
  // (1 s)/(1000000000 ns) factor to get this into the right scale.
  uint32_t high_count = (clock_freq_kilohz * (t_high_nanos + t_r_nanos) + 500000);
  return high_count / 1000000 - 3;
}

static uint32_t intel_serialio_compute_scl_lcnt(uint32_t controller_freq, uint32_t t_low_nanos,
                                                uint32_t t_f_nanos) {
  uint32_t clock_freq_kilohz = controller_freq / 1000;

  // We need low count to satisfy lowcount + 1 >= clock * (t_LOW + t_f_max)
  // Apparently the counter starts as soon as the controller pulls SCL low, so
  // include t_f to account for potential delay in falling.
  //
  // In terms of units, the division should really be thought of as a
  // (1 s)/(1000000000 ns) factor to get this into the right scale.
  uint32_t low_count = (clock_freq_kilohz * (t_low_nanos + t_f_nanos) + 500000);
  return low_count / 1000000 - 1;
}

static zx_status_t intel_serialio_compute_bus_timing(intel_serialio_i2c_device_t* device) {
  uint32_t clock_frequency = device->controller_freq;

  // These constants are from the i2c timing requirements
  uint32_t fmp_hcnt = intel_serialio_compute_scl_hcnt(clock_frequency, 260, 120);
  uint32_t fmp_lcnt = intel_serialio_compute_scl_lcnt(clock_frequency, 500, 120);
  uint32_t fs_hcnt = intel_serialio_compute_scl_hcnt(clock_frequency, 600, 300);
  uint32_t fs_lcnt = intel_serialio_compute_scl_lcnt(clock_frequency, 1300, 300);
  uint32_t ss_hcnt = intel_serialio_compute_scl_hcnt(clock_frequency, 4000, 300);
  uint32_t ss_lcnt = intel_serialio_compute_scl_lcnt(clock_frequency, 4700, 300);

  // Make sure the counts are within bounds.
  if (fmp_hcnt >= (1 << 16) || fmp_hcnt < 6 || fmp_lcnt >= (1 << 16) || fmp_lcnt < 8) {
    return ZX_ERR_OUT_OF_RANGE;
  }
  if (fs_hcnt >= (1 << 16) || fs_hcnt < 6 || fs_lcnt >= (1 << 16) || fs_lcnt < 8) {
    return ZX_ERR_OUT_OF_RANGE;
  }
  if (ss_hcnt >= (1 << 16) || ss_hcnt < 6 || ss_lcnt >= (1 << 16) || ss_lcnt < 8) {
    return ZX_ERR_OUT_OF_RANGE;
  }

  device->fmp_scl_hcnt = fmp_hcnt;
  device->fmp_scl_lcnt = fmp_lcnt;
  device->fs_scl_hcnt = fs_hcnt;
  device->fs_scl_lcnt = fs_lcnt;
  device->ss_scl_hcnt = ss_hcnt;
  device->ss_scl_lcnt = ss_lcnt;
  device->sda_hold = 1;
  return ZX_OK;
}

static zx_status_t intel_serialio_i2c_set_bus_frequency(intel_serialio_i2c_device_t* device,
                                                        uint32_t frequency) {
  if (frequency != I2C_MAX_FAST_SPEED_HZ && frequency != I2C_MAX_STANDARD_SPEED_HZ &&
      frequency != I2C_MAX_FAST_PLUS_SPEED_HZ) {
    return ZX_ERR_INVALID_ARGS;
  }

  mtx_lock(&device->mutex);
  device->bus_freq = frequency;

  zx_status_t status = intel_serialio_i2c_reset_controller(device);
  if (status != ZX_OK) {
    mtx_unlock(&device->mutex);
    return status;
  }

  mtx_unlock(&device->mutex);
  return ZX_OK;
}

static int intel_serialio_i2c_irq_thread(void* arg) {
  intel_serialio_i2c_device_t* dev = (intel_serialio_i2c_device_t*)arg;
  zx_status_t status;
  for (;;) {
    status = zx_interrupt_wait(dev->irq_handle, NULL);
    if (status != ZX_OK) {
      zxlogf(ERROR, "i2c: error waiting for interrupt: %d\n", status);
      break;
    }
    uint32_t intr_stat = readl(&dev->regs->intr_stat);
    zxlogf(SPEW, "Received i2c interrupt: %x %x\n", intr_stat, readl(&dev->regs->raw_intr_stat));
    if (intr_stat & (1u << INTR_RX_UNDER)) {
      // If we hit an underflow, it's a bug.
      zx_object_signal(dev->event_handle, 0, ERROR_DETECTED_SIGNAL);
      readl(&dev->regs->clr_rx_under);
      zxlogf(ERROR, "i2c: rx underflow detected!\n");
    }
    if (intr_stat & (1u << INTR_RX_OVER)) {
      // If we hit an overflow, it's a bug.
      zx_object_signal(dev->event_handle, 0, ERROR_DETECTED_SIGNAL);
      readl(&dev->regs->clr_rx_over);
      zxlogf(ERROR, "i2c: rx overflow detected!\n");
    }
    if (intr_stat & (1u << INTR_RX_FULL)) {
      mtx_lock(&dev->irq_mask_mutex);
      zx_object_signal(dev->event_handle, 0, RX_FULL_SIGNAL);
      RMWREG32(&dev->regs->intr_mask, INTR_RX_FULL, 1, 0);
      mtx_unlock(&dev->irq_mask_mutex);
    }
    if (intr_stat & (1u << INTR_TX_OVER)) {
      // If we hit an overflow, it's a bug.
      zx_object_signal(dev->event_handle, 0, ERROR_DETECTED_SIGNAL);
      readl(&dev->regs->clr_tx_over);
      zxlogf(ERROR, "i2c: tx overflow detected!\n");
    }
    if (intr_stat & (1u << INTR_TX_EMPTY)) {
      mtx_lock(&dev->irq_mask_mutex);
      zx_object_signal(dev->event_handle, 0, TX_EMPTY_SIGNAL);
      RMWREG32(&dev->regs->intr_mask, INTR_TX_EMPTY, 1, 0);
      mtx_unlock(&dev->irq_mask_mutex);
    }
    if (intr_stat & (1u << INTR_TX_ABORT)) {
      zxlogf(ERROR, "i2c: tx abort detected: 0x%08x\n", readl(&dev->regs->tx_abrt_source));
      zx_object_signal(dev->event_handle, 0, ERROR_DETECTED_SIGNAL);
      readl(&dev->regs->clr_tx_abort);
    }
    if (intr_stat & (1u << INTR_ACTIVITY)) {
      // Should always be masked...remask it.
      mtx_lock(&dev->irq_mask_mutex);
      RMWREG32(&dev->regs->intr_mask, INTR_ACTIVITY, 1, 0);
      mtx_unlock(&dev->irq_mask_mutex);
      zxlogf(INFO, "i2c: spurious activity irq\n");
    }
    if (intr_stat & (1u << INTR_STOP_DETECTION)) {
      zx_object_signal(dev->event_handle, 0, STOP_DETECTED_SIGNAL);
      readl(&dev->regs->clr_stop_det);
    }
    if (intr_stat & (1u << INTR_START_DETECTION)) {
      readl(&dev->regs->clr_start_det);
    }
    if (intr_stat & (1u << INTR_GENERAL_CALL)) {
      // Should always be masked...remask it.
      mtx_lock(&dev->irq_mask_mutex);
      RMWREG32(&dev->regs->intr_mask, INTR_GENERAL_CALL, 1, 0);
      mtx_unlock(&dev->irq_mask_mutex);
      zxlogf(INFO, "i2c: spurious general call irq\n");
    }
  }
  return 0;
}

zx_status_t intel_serialio_i2c_wait_for_rx_full(intel_serialio_i2c_device_t* controller,
                                                zx_time_t deadline) {
  uint32_t observed;
  zx_status_t status = zx_object_wait_one(
      controller->event_handle, RX_FULL_SIGNAL | ERROR_DETECTED_SIGNAL, deadline, &observed);
  if (status != ZX_OK) {
    return status;
  }
  if (observed & ERROR_DETECTED_SIGNAL) {
    return ZX_ERR_IO;
  }
  return ZX_OK;
}

zx_status_t intel_serialio_i2c_wait_for_tx_empty(intel_serialio_i2c_device_t* controller,
                                                 zx_time_t deadline) {
  uint32_t observed;
  zx_status_t status = zx_object_wait_one(
      controller->event_handle, TX_EMPTY_SIGNAL | ERROR_DETECTED_SIGNAL, deadline, &observed);
  if (status != ZX_OK) {
    return status;
  }
  if (observed & ERROR_DETECTED_SIGNAL) {
    return ZX_ERR_IO;
  }
  return ZX_OK;
}

zx_status_t intel_serialio_i2c_wait_for_stop_detect(intel_serialio_i2c_device_t* controller,
                                                    zx_time_t deadline) {
  uint32_t observed;
  zx_status_t status = zx_object_wait_one(
      controller->event_handle, STOP_DETECTED_SIGNAL | ERROR_DETECTED_SIGNAL, deadline, &observed);
  if (status != ZX_OK) {
    return status;
  }
  if (observed & ERROR_DETECTED_SIGNAL) {
    return ZX_ERR_IO;
  }
  return ZX_OK;
}

zx_status_t intel_serialio_i2c_check_for_error(intel_serialio_i2c_device_t* controller) {
  uint32_t observed;
  zx_status_t status =
      zx_object_wait_one(controller->event_handle, ERROR_DETECTED_SIGNAL, 0, &observed);
  if (status != ZX_OK && status != ZX_ERR_TIMED_OUT) {
    return status;
  }
  if (observed & ERROR_DETECTED_SIGNAL) {
    return ZX_ERR_IO;
  }
  return ZX_OK;
}

zx_status_t intel_serialio_i2c_clear_stop_detect(intel_serialio_i2c_device_t* controller) {
  return zx_object_signal(controller->event_handle, STOP_DETECTED_SIGNAL, 0);
}

// Perform a write to the DATA_CMD register, and clear
// interrupt masks as appropriate
zx_status_t intel_serialio_i2c_issue_rx(intel_serialio_i2c_device_t* controller,
                                        uint32_t data_cmd) {
  writel(data_cmd, &controller->regs->data_cmd);
  return ZX_OK;
}

zx_status_t intel_serialio_i2c_flush_rx_full_irq(intel_serialio_i2c_device_t* controller) {
  mtx_lock(&controller->irq_mask_mutex);
  zx_status_t status = zx_object_signal(controller->event_handle, RX_FULL_SIGNAL, 0);
  RMWREG32(&controller->regs->intr_mask, INTR_RX_FULL, 1, 1);
  mtx_unlock(&controller->irq_mask_mutex);
  return status;
}

zx_status_t intel_serialio_i2c_read_rx(intel_serialio_i2c_device_t* controller, uint8_t* data) {
  *data = readl(&controller->regs->data_cmd);
  return ZX_OK;
}

zx_status_t intel_serialio_i2c_issue_tx(intel_serialio_i2c_device_t* controller,
                                        uint32_t data_cmd) {
  writel(data_cmd, &controller->regs->data_cmd);
  uint32_t tx_tl;
  intel_serialio_i2c_get_tx_fifo_threshold(controller, &tx_tl);
  const uint32_t txflr = readl(&controller->regs->txflr) & 0x1ff;
  // If we've raised the TX queue level above the threshold, clear the signal
  // and unmask the interrupt.
  if (txflr > tx_tl) {
    mtx_lock(&controller->irq_mask_mutex);
    zx_status_t status = zx_object_signal(controller->event_handle, TX_EMPTY_SIGNAL, 0);
    RMWREG32(&controller->regs->intr_mask, INTR_TX_EMPTY, 1, 1);
    mtx_unlock(&controller->irq_mask_mutex);
    return status;
  }
  return ZX_OK;
}

void intel_serialio_i2c_get_rx_fifo_threshold(intel_serialio_i2c_device_t* controller,
                                              uint32_t* threshold) {
  *threshold = (readl(&controller->regs->rx_tl) & 0xff) + 1;
}

// Get an RX interrupt whenever the RX FIFO size is >= the threshold.
zx_status_t intel_serialio_i2c_set_rx_fifo_threshold(intel_serialio_i2c_device_t* controller,
                                                     uint32_t threshold) {
  if (threshold - 1 > UINT8_MAX) {
    return ZX_ERR_INVALID_ARGS;
  }

  RMWREG32(&controller->regs->rx_tl, 0, 8, threshold - 1);
  return ZX_OK;
}

void intel_serialio_i2c_get_tx_fifo_threshold(intel_serialio_i2c_device_t* controller,
                                              uint32_t* threshold) {
  *threshold = (readl(&controller->regs->tx_tl) & 0xff) + 1;
}

// Get a TX interrupt whenever the TX FIFO size is <= the threshold.
zx_status_t intel_serialio_i2c_set_tx_fifo_threshold(intel_serialio_i2c_device_t* controller,
                                                     uint32_t threshold) {
  if (threshold - 1 > UINT8_MAX) {
    return ZX_ERR_INVALID_ARGS;
  }

  RMWREG32(&controller->regs->tx_tl, 0, 8, threshold - 1);
  return ZX_OK;
}

static void intel_serialio_i2c_unbind(void* ctx) {
  intel_serialio_i2c_device_t* dev = ctx;
  if (dev) {
    zxlogf(INFO, "intel-i2c: unbind irq_handle %d irq_thread %p\n", dev->irq_handle,
           dev->irq_thread);
    if ((dev->irq_handle != ZX_HANDLE_INVALID) && dev->irq_thread) {
      zx_interrupt_destroy(dev->irq_handle);
      thrd_join(dev->irq_thread, NULL);
    }
    if (dev->zxdev) {
      device_remove(dev->zxdev);
    }
  }
}

static void intel_serialio_i2c_release(void* ctx) {
  intel_serialio_i2c_device_t* dev = ctx;
  if (dev) {
    mmio_buffer_release(&dev->mmio);
    zx_handle_close(dev->irq_handle);
    zx_handle_close(dev->event_handle);
  }
  free(dev);
}

static zx_protocol_device_t intel_serialio_i2c_device_proto = {
    .version = DEVICE_OPS_VERSION,
    .unbind = intel_serialio_i2c_unbind,
    .release = intel_serialio_i2c_release,
};

// The controller lock should already be held when entering this function.
zx_status_t intel_serialio_i2c_reset_controller(intel_serialio_i2c_device_t* device) {
  zx_status_t status = ZX_OK;

  // The register will only return valid values if the ACPI _PS0 has been
  // evaluated.
  if (readl((void*)device->regs + DEVIDLE_CONTROL) != 0xffffffff) {
    // Wake up device if it is in DevIdle state
    RMWREG32((void*)device->regs + DEVIDLE_CONTROL, DEVIDLE_CONTROL_DEVIDLE, 1, 0);

    // Wait for wakeup to finish processing
    int retry = 10;
    while (retry-- && (readl((void*)device->regs + DEVIDLE_CONTROL) &
                       (1 << DEVIDLE_CONTROL_CMD_IN_PROGRESS))) {
      usleep(10);
    }
    if (!retry) {
      printf("i2c-controller: timed out waiting for device idle\n");
      return ZX_ERR_TIMED_OUT;
    }
  }

  // Reset the device.
  RMWREG32(device->soft_reset, 0, 2, 0x0);
  RMWREG32(device->soft_reset, 0, 2, 0x3);

  // Clear the "Restore Required" flag
  RMWREG32((void*)device->regs + DEVIDLE_CONTROL, DEVIDLE_CONTROL_RESTORE_REQUIRED, 1, 0);

  // Disable the controller.
  RMWREG32(&device->regs->i2c_en, I2C_EN_ENABLE, 1, 0);

  // Reconfigure the bus timing
  if (device->bus_freq == I2C_MAX_FAST_PLUS_SPEED_HZ) {
    RMWREG32(&device->regs->fs_scl_hcnt, 0, 16, device->fmp_scl_hcnt);
    RMWREG32(&device->regs->fs_scl_lcnt, 0, 16, device->fmp_scl_lcnt);
  } else {
    RMWREG32(&device->regs->fs_scl_hcnt, 0, 16, device->fs_scl_hcnt);
    RMWREG32(&device->regs->fs_scl_lcnt, 0, 16, device->fs_scl_lcnt);
  }
  RMWREG32(&device->regs->ss_scl_hcnt, 0, 16, device->ss_scl_hcnt);
  RMWREG32(&device->regs->ss_scl_lcnt, 0, 16, device->ss_scl_lcnt);
  RMWREG32(&device->regs->sda_hold, 0, 16, device->sda_hold);

  unsigned int speed = CTL_SPEED_STANDARD;
  if (device->bus_freq == I2C_MAX_FAST_SPEED_HZ || device->bus_freq == I2C_MAX_FAST_PLUS_SPEED_HZ) {
    speed = CTL_SPEED_FAST;
  }

  writel((0x1 << CTL_SLAVE_DISABLE) | (0x1 << CTL_RESTART_ENABLE) | (speed << CTL_SPEED) |
             (CTL_MASTER_MODE_ENABLED << CTL_MASTER_MODE),
         &device->regs->ctl);

  mtx_lock(&device->irq_mask_mutex);
  // Mask all interrupts
  writel(0, &device->regs->intr_mask);

  if (readl(&device->regs->comp_type) == INTEL_DESIGNWARE_COMP_TYPE) {
    uint32_t param = readl(&device->regs->comp_param1);
    device->tx_fifo_depth = intel_i2c_extract_tx_fifo_depth_from_param(param);
    device->rx_fifo_depth = intel_i2c_extract_rx_fifo_depth_from_param(param);
  } else {
    device->tx_fifo_depth = 8;
    device->rx_fifo_depth = 8;
  }

  status = intel_serialio_i2c_set_rx_fifo_threshold(device, DEFAULT_RX_FIFO_TRIGGER_LEVEL);
  if (status != ZX_OK) {
    goto cleanup;
  }
  status = intel_serialio_i2c_set_tx_fifo_threshold(device, DEFAULT_TX_FIFO_TRIGGER_LEVEL);
  if (status != ZX_OK) {
    goto cleanup;
  }

  // Clear the signals
  status = zx_object_signal(
      device->event_handle,
      RX_FULL_SIGNAL | TX_EMPTY_SIGNAL | STOP_DETECTED_SIGNAL | ERROR_DETECTED_SIGNAL, 0);
  if (status != ZX_OK) {
    goto cleanup;
  }

  // Reading this register clears all interrupts.
  readl(&device->regs->clr_intr);

  // Unmask the interrupts we care about
  writel((1u << INTR_STOP_DETECTION) | (1u << INTR_TX_ABORT) | (1u << INTR_TX_EMPTY) |
             (1u << INTR_TX_OVER) | (1u << INTR_RX_FULL) | (1u << INTR_RX_OVER) |
             (1u << INTR_RX_UNDER),
         &device->regs->intr_mask);

cleanup:
  mtx_unlock(&device->irq_mask_mutex);
  return status;
}

static zx_status_t intel_serialio_i2c_device_specific_init(intel_serialio_i2c_device_t* device,
                                                           uint16_t device_id) {
  static const struct {
    uint16_t device_ids[16];
    // Offset of the soft reset register
    size_t reset_offset;
    // Internal controller frequency, in hertz
    uint32_t controller_clock_frequency;
  } dev_props[] = {
      {
          .device_ids =
              {
                  INTEL_SUNRISE_POINT_SERIALIO_I2C0_DID,
                  INTEL_SUNRISE_POINT_SERIALIO_I2C1_DID,
                  INTEL_SUNRISE_POINT_SERIALIO_I2C2_DID,
                  INTEL_SUNRISE_POINT_SERIALIO_I2C3_DID,
                  INTEL_SUNRISE_POINT_SERIALIO_I2C4_DID,
              },
          .reset_offset = 0x204,
          .controller_clock_frequency = 120 * 1000 * 1000,
      },
      {
          .device_ids =
              {
                  INTEL_WILDCAT_POINT_SERIALIO_I2C0_DID,
                  INTEL_WILDCAT_POINT_SERIALIO_I2C1_DID,
              },
          .reset_offset = 0x804,
          .controller_clock_frequency = 100 * 1000 * 1000,
      },
  };

  for (unsigned int i = 0; i < countof(dev_props); ++i) {
    const unsigned int num_dev_ids = countof(dev_props[0].device_ids);
    for (unsigned int dev_idx = 0; dev_idx < num_dev_ids; ++dev_idx) {
      if (!dev_props[i].device_ids[dev_idx]) {
        break;
      }
      if (dev_props[i].device_ids[dev_idx] != device_id) {
        continue;
      }

      device->controller_freq = dev_props[i].controller_clock_frequency;
      device->soft_reset = (void*)device->regs + dev_props[i].reset_offset;
      return ZX_OK;
    }
  }

  return ZX_ERR_NOT_SUPPORTED;
}

static void intel_serialio_add_devices(intel_serialio_i2c_device_t* parent, pci_protocol_t* pci) {
  // get child info from aux data, max 4
  // TODO: this seems nonstandard to device model
  auxdata_i2c_device_t childdata[4];
  memset(childdata, 0, sizeof(childdata));

  size_t actual;
  zx_status_t status = pci_get_auxdata(pci, "i2c-child", childdata, sizeof(childdata), &actual);
  if (status != ZX_OK) {
    return;
  }

  auxdata_i2c_device_t* child = &childdata[0];
  uint32_t count = actual / sizeof(auxdata_i2c_device_t);
  uint32_t bus_speed = 0;
  while (count--) {
    zxlogf(TRACE,
           "i2c: got child[%u] bus_controller=%d ten_bit=%d address=0x%x bus_speed=%u"
           " protocol_id=0x%08x\n",
           count, child->is_bus_controller, child->ten_bit, child->address, child->bus_speed,
           child->protocol_id);

    if (bus_speed && bus_speed != child->bus_speed) {
      zxlogf(ERROR, "i2c: cannot add devices with different bus speeds (%u, %u)\n", bus_speed,
             child->bus_speed);
    }
    if (!bus_speed) {
      intel_serialio_i2c_set_bus_frequency(parent, child->bus_speed);
      bus_speed = child->bus_speed;
    }
    intel_serialio_i2c_add_subordinate(
        parent, child->ten_bit ? I2C_10BIT_ADDRESS : I2C_7BIT_ADDRESS, child->address,
        child->protocol_id, child->props, child->propcount);
    child += 1;
  }
}

zx_status_t intel_i2c_bind(void* ctx, zx_device_t* dev) {
  pci_protocol_t pci;
  if (device_get_protocol(dev, ZX_PROTOCOL_PCI, &pci))
    return ZX_ERR_NOT_SUPPORTED;

  intel_serialio_i2c_device_t* device = calloc(1, sizeof(*device));
  if (!device)
    return ZX_ERR_NO_MEMORY;

  list_initialize(&device->subordinate_list);
  mtx_init(&device->mutex, mtx_plain);
  mtx_init(&device->irq_mask_mutex, mtx_plain);
  device->pcidev = dev;

  uint16_t vendor_id;
  uint16_t device_id;
  pci_config_read16(&pci, PCI_CONFIG_VENDOR_ID, &vendor_id);
  pci_config_read16(&pci, PCI_CONFIG_DEVICE_ID, &device_id);

  zx_status_t status = pci_map_bar_buffer(&pci, 0u, ZX_CACHE_POLICY_UNCACHED_DEVICE, &device->mmio);
  if (status != ZX_OK) {
    zxlogf(ERROR, "i2c: failed to mape pci bar 0: %d\n", status);
    goto fail;
  }
  device->regs = device->mmio.vaddr;

  // set msi irq mode
  status = pci_set_irq_mode(&pci, ZX_PCIE_IRQ_MODE_LEGACY, 1);
  if (status < 0) {
    zxlogf(ERROR, "i2c: failed to set irq mode: %d\n", status);
    goto fail;
  }

  // get irq handle
  status = pci_map_interrupt(&pci, 0, &device->irq_handle);
  if (status != ZX_OK) {
    zxlogf(ERROR, "i2c: failed to get irq handle: %d\n", status);
    goto fail;
  }

  status = zx_event_create(0, &device->event_handle);
  if (status != ZX_OK) {
    zxlogf(ERROR, "i2c: failed to create event handle: %d\n", status);
    goto fail;
  }

  // start irq thread
  int ret =
      thrd_create_with_name(&device->irq_thread, intel_serialio_i2c_irq_thread, device, "i2c-irq");
  if (ret != thrd_success) {
    zxlogf(ERROR, "i2c: failed to create irq thread: %d\n", ret);
    goto fail;
  }

  // Run the bus at standard speed by default.
  device->bus_freq = I2C_MAX_STANDARD_SPEED_HZ;

  status = intel_serialio_i2c_device_specific_init(device, device_id);
  if (status != ZX_OK) {
    zxlogf(ERROR, "i2c: device specific init failed: %d\n", status);
    goto fail;
  }

  status = intel_serialio_compute_bus_timing(device);
  if (status < 0) {
    zxlogf(ERROR, "i2c: compute bus timing failed: %d\n", status);
    goto fail;
  }

  // Temporary hack until we have routed through the FMCN ACPI tables.
  if (vendor_id == INTEL_VID && device_id == INTEL_SUNRISE_POINT_SERIALIO_I2C0_DID) {
    // TODO: These should all be extracted from FPCN in the ACPI tables.
    device->fmp_scl_lcnt = 0x0042;
    device->fmp_scl_hcnt = 0x001b;
    device->sda_hold = 0x24;
  } else if (vendor_id == INTEL_VID && device_id == INTEL_SUNRISE_POINT_SERIALIO_I2C1_DID) {
    // TODO(yky): These should all be extracted from FMCN in the ACPI tables.
    device->fs_scl_lcnt = 0x00b6;
    device->fs_scl_hcnt = 0x0059;
    device->sda_hold = 0x24;
  } else if (vendor_id == INTEL_VID && device_id == INTEL_SUNRISE_POINT_SERIALIO_I2C2_DID) {
    // TODO: These should all be extracted from FMCN in the ACPI tables.
    device->fs_scl_lcnt = 0x00ba;
    device->fs_scl_hcnt = 0x005d;
    device->sda_hold = 0x24;
  } else if (vendor_id == INTEL_VID && device_id == INTEL_SUNRISE_POINT_SERIALIO_I2C4_DID) {
    // TODO: These should all be extracted from FMCN in the ACPI tables.
    device->fs_scl_lcnt = 0x005a;
    device->fs_scl_hcnt = 0x00a6;
    device->sda_hold = 0x24;
  }

  // Configure the I2C controller. We don't need to hold the lock because
  // nobody else can see this controller yet.
  status = intel_serialio_i2c_reset_controller(device);
  if (status < 0) {
    zxlogf(ERROR, "i2c: reset controller failed: %d\n", status);
    goto fail;
  }

  char name[ZX_DEVICE_NAME_MAX];
  snprintf(name, sizeof(name), "i2c-bus-%04x", device_id);

  device_add_args_t args = {
      .version = DEVICE_ADD_ARGS_VERSION,
      .name = name,
      .ctx = device,
      .ops = &intel_serialio_i2c_device_proto,
  };

  status = device_add(dev, &args, &device->zxdev);
  if (status < 0) {
    zxlogf(ERROR, "device add failed: %d\n", status);
    goto fail;
  }

  zxlogf(INFO,
         "initialized intel serialio i2c driver, "
         "reg=%p regsize=%ld\n",
         device->regs, device->mmio.size);

  intel_serialio_add_devices(device, &pci);
  return ZX_OK;

fail:
  intel_serialio_i2c_unbind(device);
  intel_serialio_i2c_release(device);
  return status;
}

static zx_driver_ops_t intel_i2c_driver_ops = {
    .version = DRIVER_OPS_VERSION,
    .bind = intel_i2c_bind,
};

ZIRCON_DRIVER_BEGIN(intel_i2c, intel_i2c_driver_ops, "zircon", "0.1", 9)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_PCI), BI_ABORT_IF(NE, BIND_PCI_VID, 0x8086),
    BI_MATCH_IF(EQ, BIND_PCI_DID, INTEL_WILDCAT_POINT_SERIALIO_I2C0_DID),
    BI_MATCH_IF(EQ, BIND_PCI_DID, INTEL_WILDCAT_POINT_SERIALIO_I2C1_DID),
    BI_MATCH_IF(EQ, BIND_PCI_DID, INTEL_SUNRISE_POINT_SERIALIO_I2C0_DID),
    BI_MATCH_IF(EQ, BIND_PCI_DID, INTEL_SUNRISE_POINT_SERIALIO_I2C1_DID),
    BI_MATCH_IF(EQ, BIND_PCI_DID, INTEL_SUNRISE_POINT_SERIALIO_I2C2_DID),
    BI_MATCH_IF(EQ, BIND_PCI_DID, INTEL_SUNRISE_POINT_SERIALIO_I2C3_DID),
    BI_MATCH_IF(EQ, BIND_PCI_DID, INTEL_SUNRISE_POINT_SERIALIO_I2C4_DID),
    ZIRCON_DRIVER_END(intel_i2c)
